using System;
using System.Diagnostics;

public class NetBuffer
{
    private byte[] m_buffer;
    private int m_rpos;
    private int m_wpos;

    public NetBuffer(int capacity = 256)
    {
        m_buffer = new byte[capacity];
    }

    public NetBuffer(byte[] buffer, int rpos, int wpos)
    {
        m_buffer = buffer;
        m_rpos = rpos;
        m_wpos = wpos;
    }

    public byte[] GetBuffer()
    {
        return m_buffer;
    }
    public int GetSize()
    {
        return m_wpos;
    }
    public int GetPosition()
    {
        return m_rpos;
    }

    public void Erlarge(int size)
    {
        Debug.Assert(size >= m_wpos);
        CheckWriteSize(size);
        m_wpos = size;
    }
    public void Shrink(int size)
    {
        Debug.Assert(size <= m_wpos);
        m_wpos = size;
        m_rpos = Math.Min(m_rpos, size);
    }

    public void ResetReadPosition()
    {
        m_rpos = 0;
    }
    public void AdjustReadPosition(int size)
    {
        m_rpos += size;
    }
    public void SkipReadPositionToEnd()
    {
        m_rpos = m_wpos;
    }

    public bool IsReadableEmpty()
    {
        return m_rpos >= m_wpos;
    }
    public int GetReadableSize()
    {
        return m_wpos - m_rpos;
    }

    public byte[] GetReadableBytes()
    {
        byte[] bytes = null;
        int n = Math.Max(m_wpos - m_rpos, 0);
        if (n != 0)
        {
            bytes = new byte[n];
            Buffer.BlockCopy(m_buffer, m_rpos, bytes, 0, n);
        }
        return bytes;
    }

    public int Skip(int count)
    {
        return InternalSkip(count);
    }

    public void Append(byte[] buffer, int offset = 0, int count = -1)
    {
        InternalWrite(buffer, offset, count);
    }
    public void Take(byte[] buffer, int offset = 0, int count = -1)
    {
        InternalRead(buffer, offset, count);
    }

    public void Write(char value)
    {
        InternalWriteByte((byte)value);
    }
    public void Write(bool value)
    {
        InternalWriteByte((byte)(value ? 1 : 0));
    }
    public void Write(float value)
    {
        InternalWrite(BitConverter.GetBytes(value));
    }
    public void Write(double value)
    {
        InternalWrite(BitConverter.GetBytes(value));
    }
    public void Write(sbyte value)
    {
        InternalWriteByte((byte)value);
    }
    public void Write(byte value)
    {
        InternalWriteByte(value);
    }
    public void Write(short value)
    {
        InternalWrite(BitConverter.GetBytes(value));
    }
    public void Write(ushort value)
    {
        InternalWrite(BitConverter.GetBytes(value));
    }
    public void Write(int value)
    {
        InternalWrite(BitConverter.GetBytes(value));
    }
    public void Write(uint value)
    {
        InternalWrite(BitConverter.GetBytes(value));
    }
    public void Write(long value)
    {
        InternalWrite(BitConverter.GetBytes(value));
    }
    public void Write(ulong value)
    {
        InternalWrite(BitConverter.GetBytes(value));
    }
    public void Write(string value)
    {
        Write(value != null && value.Length != 0 ? System.Text.Encoding.UTF8.GetBytes(value) : null);
    }
    public void Write(byte[] value)
    {
        int length = value != null ? value.Length : 0;
        Write((ushort)length);
        if (length != 0)
        {
            InternalWrite(value);
        }
    }

    public void Read(out char value)
    {
        value = (char)m_buffer[InternalSkip(sizeof(byte))];
    }
    public void Read(out bool value)
    {
        value = m_buffer[InternalSkip(sizeof(byte))] != 0;
    }
    public void Read(out float value)
    {
        value = BitConverter.ToSingle(m_buffer, InternalSkip(sizeof(float)));
    }
    public void Read(out double value)
    {
        value = BitConverter.ToDouble(m_buffer, InternalSkip(sizeof(double)));
    }
    public void Read(out sbyte value)
    {
        value = (sbyte)m_buffer[InternalSkip(sizeof(sbyte))];
    }
    public void Read(out byte value)
    {
        value = m_buffer[InternalSkip(sizeof(byte))];
    }
    public void Read(out short value)
    {
        value = BitConverter.ToInt16(m_buffer, InternalSkip(sizeof(short)));
    }
    public void Read(out ushort value)
    {
        value = BitConverter.ToUInt16(m_buffer, InternalSkip(sizeof(ushort)));
    }
    public void Read(out int value)
    {
        value = BitConverter.ToInt32(m_buffer, InternalSkip(sizeof(int)));
    }
    public void Read(out uint value)
    {
        value = BitConverter.ToUInt32(m_buffer, InternalSkip(sizeof(uint)));
    }
    public void Read(out long value)
    {
        value = BitConverter.ToInt64(m_buffer, InternalSkip(sizeof(long)));
    }
    public void Read(out ulong value)
    {
        value = BitConverter.ToUInt64(m_buffer, InternalSkip(sizeof(ulong)));
    }
    public void Read(out string value)
    {
        Read(out ushort n);
        if (n != 0)
        {
            value = System.Text.Encoding.UTF8.GetString(m_buffer, InternalSkip(n), n);
        }
        else
        {
            value = null;
        }
    }
    public void Read(out byte[] value)
    {
        Read(out ushort n);
        if (n != 0)
        {
            value = new byte[n];
            InternalRead(value);
        }
        else
        {
            value = null;
        }
    }

    private void InternalWrite(byte[] buffer, int offset = 0, int count = -1)
    {
        count = count != -1 ? count : buffer.Length - offset;
        CheckWriteSize(count);
        Buffer.BlockCopy(buffer, offset, m_buffer, m_wpos, count);
        m_wpos += count;
    }
    private void InternalWriteByte(byte value)
    {
        CheckWriteSize(1);
        m_buffer[m_wpos] = value;
        m_wpos += 1;
    }

    private void InternalRead(byte[] buffer, int offset = 0, int count = -1)
    {
        count = count != -1 ? count : buffer.Length - offset;
        CheckReadSize(count);
        Buffer.BlockCopy(m_buffer, m_rpos, buffer, offset, count);
        m_rpos += count;
    }
    private int InternalSkip(int count)
    {
        CheckReadSize(count);
        m_rpos += count;
        return m_rpos - count;
    }

    private void CheckWriteSize(int size)
    {
        if (m_wpos + size > m_buffer.Length)
        {
            var buffer = new byte[Math.Max(m_wpos + size, m_buffer.Length << 1)];
            Buffer.BlockCopy(m_buffer, 0, buffer, 0, m_wpos);
            m_buffer = buffer;
        }
    }

    private void CheckReadSize(int size)
    {
        if (m_rpos + size > m_wpos)
        {
            throw new ArgumentException(String.Format(
                "out of range: rpos={0},wpos={1},size={2}", m_rpos, m_wpos, size));
        }
    }
}
